package com.lzp.java.concurrent.buildcache;

import java.util.Map;
import java.util.concurrent.*;

/**
 * @author lzp
 * @Description: 1. 设置缓存失效 2 为防止缓存在同一时间大量失效，设置缓存时间随机
 * @date 2021/3/14
 */
public class Cache6<K, V> implements Computable<K, V> {
    private final Map<K, Future<V>> cache = new ConcurrentHashMap<>();
    private final Computable<K, V> c;

    public Cache6(Computable<K, V> c) {
        this.c = c;
    }

    @Override
    public V compute(K key) throws Exception {
        while (true) {
            Future<V> result = cache.get(key);
            if (result == null) {
                FutureTask<V> futureTask = new FutureTask<V>(() -> {
                    return c.compute(key);
                });
                result = futureTask;
                // 如果第一次缓存失败，放入的key-value是不正确的。
                result = cache.putIfAbsent(key, result);
                if (result == null) {
                    result = futureTask;
                    futureTask.run();
                }
            }
            try {
                return result.get();
            } catch (InterruptedException e) {
                cache.remove(key);
                throw e;
            } catch (ExecutionException e) {
                System.out.println("异常执行重试。。。");
                cache.remove(key); // 清除污染缓存！！
                // cache.replace() 新思路 生成一个新的任务，然后替换原来的任务。
            }
        }
    }

    // 定时任务--每次调用compute(A arg, long expire); 查数据都会把删除缓存的任务放进线程池
    public final static ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

    public V computeRandomExpire(K key) throws Exception {
        return compute(key, (long) (Math.random() * 1000));
    }

    // 缓存超时失效
    public V compute(K key, long expire) throws Exception {
        if (expire > 0) {
            // 将缓存任务扔到线程池中
            pool.schedule(() -> {
                expire(key); // 超时时间结束，清除缓存
            }, expire, TimeUnit.MILLISECONDS);
        }
        return compute(key);
    }

    private synchronized void expire(K key) {
        Future<V> future = cache.get(key);
        if (future != null) {
            if (future.isDone()) {
                System.out.println("任务已经被取消");
                future.cancel(true);
            }
            System.out.println("清除缓存。。。");
            cache.remove(key);
        }
    }

    public static void main(String[] args) throws Exception {
        Cache6<String, Integer> cache1 = new Cache6<>(new MayFailImpl());
        System.out.println(cache1.compute("key", 3000));
        TimeUnit.SECONDS.sleep(5);
        System.out.println(cache1.compute("key", 3000));
    }
}
